#include "force-dir.hpp"
#include "containers.h"
#include "types.h"
#include <cstdio>
#include <cstdlib>

namespace Trade {
/*

        _,'|             _.-''``-...___..--';)
       /_ \'.      __..-' ,      ,--...--'''
      <\    .`--'''       `     /'
       `-';'               ;   ; ;
 __...--''     ___...--_..'  .;.'
(,__....----'''       (,..--''   Felix Lee <flee@cse.psu.edu>

*/

void
ForceDir::copy(const ForceDir& fd) noexcept
{
  memcpy((void*)(this), &fd, sizeof(ForceDir));
}

DIR_DREAMER_TYPE
ForceDir::changed(const ForceDir& fd,
                  ForceLists& fl,
                  const time_t ts,
                  const double hpx) noexcept
{
  event_t et = 0;
  DIR_DREAMER_TYPE dt = DIR_DREAMER_TYPE::DD_UNKNOW;
  const int max_array_index = 4;
  DIR_DREAMER_TYPE ddt[max_array_index] = { DIR_DREAMER_TYPE::DD_UNKNOW };
  dir_t d[max_array_index] = { 0 };
  // if (fd.cg_far_rt != cg_far_rt) {
  //   et |= FE_CG_FAR_RT_CHANGED;
  //   d[0] = fd.cg_far_rt;
  //   fl.df_far_rt_cg.new_dir_maker(fd.cg_far_rt, ts, hpx);
  //   ddt[0] = fl.df_far_rt_cg.retrive_dir_type(fd.cg_far_rt, ts);
  // }
  if (fd.cg_far_st != cg_far_st) {
    et |= FE_CG_FAR_ST_CHANGED;
    d[1] = fd.cg_far_st;
    fl.df_far_78_cg.new_dir_maker(fd.cg_far_st, ts, hpx);
    ddt[1] = fl.df_far_78_cg.retrive_dir_type(fd.cg_far_st, ts);
  }
  // if (fd.cg_near_rt != cg_near_rt) {
  //   et |= FE_CG_NEAR_RT_CHANGED;
  //   d[2] = fd.cg_near_rt;
  //   fl.df_near_rt_cg.new_dir_maker(fd.cg_near_rt, ts, hpx);
  //   ddt[2] = fl.df_near_rt_cg.retrive_dir_type(fd.cg_near_rt, ts);
  // }
  if (fd.cg_near_st != cg_near_st) {
    et |= FE_CG_NEAR_ST_CHANGED;
    d[3] = fd.cg_near_st;
    fl.df_near_78_cg.new_dir_maker(fd.cg_near_st, ts, hpx);
    ddt[3] = fl.df_near_78_cg.retrive_dir_type(fd.cg_near_st, ts);
  }

  if (et) {
    copy(fd);

    // one dir ?
    dir_t dd = 0;
    for (int8_t i = 0; i < max_array_index; ++i) {
      if (!dd) {
        if (d[i]) {
          dd = d[i];
          dt = dd > 0 ? DIR_DREAMER_TYPE::DD_UP : DIR_DREAMER_TYPE::DD_DN;
        }
      } else {
        if (d[i] && (d[i] != dd)) {
          dt = DIR_DREAMER_TYPE::DD_SWING;
          break;
        }
      }
    }

    // one direction, get the larger ddt
    if (dt != DIR_DREAMER_TYPE::DD_UNKNOW && dt != DIR_DREAMER_TYPE::DD_SWING) {
      for (int8_t i = 0; i < max_array_index; ++i) {
        if (ddt[i] > dt)
          dt = ddt[i];
      }
    }
  }

  return dt;
}

void
convert_fl_to_fl3(ForceLists& fl, ForceLists3& fl3)
{
  fl.df_near_78_st.conveter_to_3(fl3.df_near_78_st);
  fl.df_far_78_st.conveter_to_3(fl3.df_far_78_st);
  fl.df_near_rt_st.conveter_to_3(fl3.df_near_rt_st);
  fl.df_far_rt_st.conveter_to_3(fl3.df_far_rt_st);
  fl.df_near_78_cg.conveter_to_3(fl3.df_near_78_cg);
  fl.df_far_78_cg.conveter_to_3(fl3.df_far_78_cg);
  fl.df_near_rt_cg.conveter_to_3(fl3.df_near_rt_cg);
  fl.df_far_rt_cg.conveter_to_3(fl3.df_far_rt_cg);
}

void
print_price_force(const PriceDelta& pd [[maybe_unused]],
                  const NearFarTrend& nft,
                  const ForceLevel& flv_79,
                  const time_t t_now) noexcept
{
  // printf("_PD_");
  // for (const auto& p : pd) {
  //   printf(" %.02lf", p);
  // }
#if (STONE_DEBUG || STEP_BY_STEP)
  printf("\n");
  printf("Nft: %d %d, %ld, %ld | ",
         nft.d_near,
         nft.d_far,
         (nft.t1 - t_now) / 60,
         (nft.t2 - t_now) / 60);
  printf("Force NEAR %d %d; FAR %d %d\n",
         flv_79.d_near_10,
         // flv_79.l_usdt_10,

         flv_79.d_near_rt, // flv_79.d_usdt_rt
         // flv_79.l_usdt_rt,

         flv_79.d_far_10,
         // flv_79.l_usd_10,

         flv_79.d_far_rt // flv_79.d_usd_rt
                         // flv_79.l_usd_rt);
  );
#endif
}

event_t
force_79_changed(const ForceLevel& fl_new, const ForceLevel& fl_old) noexcept
{
  event_t e = 0;
  if (fl_old.d_near_rt != fl_new.d_near_rt) {
    e |= EVENT_FORCE_NEAR_RT_CHANGED;
  }
  if (fl_old.d_near_10 != fl_new.d_near_10) {
    e |= EVENT_FORCE_NEAR_10_CHANGED;
  }
  if (fl_old.d_far_rt != fl_new.d_far_rt) {
    e |= EVENT_FORCE_FAR_RT_CHANGED;
  }
  if (fl_old.d_far_10 != fl_new.d_far_10) {
    e |= EVENT_FORCE_FAR_10_CHANGED;
  }

  return e;
}

/*

                       .-.
                      |_:_|
                     /(_Y_)\
.                   ( \/M\/ )
 '.               _.'-/'-'\-'._
   ':           _/.--'[[[[]'--.\_
     ':        /_'  : |::"| :  '.\
       ':     //   ./ |oUU| \.'  :\
         ':  _:'..' \_|___|_/ :   :|
           ':.  .'  |_[___]_|  :.':\
            [::\ |  :  | |  :   ; : \
             '-'   \/'.| |.' \  .;.' |
             |\_    \  '-'   :       |
             |  \    \ .:    :   |   |
             |   \    | '.   :    \  |
             /       \   :. .;       |
            /     |   |  :__/     :  \\
           |  |   |    \:   | \   |   ||
          /    \  : :  |:   /  |__|   /|
      snd |     : : :_/_|  /'._\  '--|_\
          /___.-/_|-'   \  \
                         '-'

*/

Force::Force()
{
  set_force_data();
  memset((void*)(fs_data), 0, sizeof(ForceSpot) * deep_ * 2);
}

Force::~Force() {}

ca_iter
Force::near_front() noexcept
{
  return ca_front(&ca_near);
}

ca_iter
Force::near_back() noexcept
{
  return ca_back(&ca_near);
}
ca_iter
Force::near_current() noexcept
{
  return ca_current(&ca_near);
}
ca_iter
Force::near_next(ca_iter it) noexcept
{
  return ca_next_(&ca_near, it);
}
ca_iter
Force::near_prev(ca_iter it) noexcept
{
  return ca_prev_(&ca_near, it);
}

ForceSpot*
Force::get_near_force_spot(const ca_iter it) noexcept
{
  return (ForceSpot*)(ca_get_data(&ca_near, it));
}

ca_iter
Force::far_front() noexcept
{
  return ca_front(&ca_far);
}

ca_iter
Force::far_back() noexcept
{
  return ca_back(&ca_far);
}
ca_iter
Force::far_current() noexcept
{
  return ca_current(&ca_far);
}
ca_iter
Force::far_next(ca_iter it) noexcept
{
  return ca_next_(&ca_far, it);
}
ca_iter
Force::far_prev(ca_iter it) noexcept
{
  return ca_prev_(&ca_far, it);
}

ForceSpot*
Force::get_far_force_spot(const ca_iter it) noexcept
{
  return (ForceSpot*)(ca_get_data(&ca_far, it));
}

void
Force::set_force_data() noexcept
{
  init_circle_array(&ca_near, fs_data, sizeof(ForceSpot), deep_);
  init_circle_array(&ca_far, fs_data + deep_, sizeof(ForceSpot), deep_);
}

void
Force::read_data() noexcept
{
  set_force_data();

  // DO NOT REMOVE FOR SAVE_SPACE
  // auto read_data = [&](CircleArray* ca) {
  //   ForceSpot* fs = (ForceSpot*)(ca->data_ptr);
  //   time_t max_ts = 0, min_ts = 0;
  //   for (usize_t i = 0; i < deep_; ++i) {
  //     if (fs[i].t) {
  //       if (!max_ts) {
  //         ca->s = ca->c = i;
  //         min_ts = max_ts = fs[i].t;
  //       } else {
  //         if (fs[i].t > max_ts) {
  //           ca->c = i;
  //           max_ts = fs[i].t;
  //         }
  //         if (fs[i].t < min_ts) {
  //           ca->s = i;
  //           min_ts = fs[i].t;
  //         }
  //       }
  //     }
  //   }
  //   if (max_ts) {
  //     ca->e = ca->s + 1;
  //     ca->siz = (ca->e > ca->s) ? (ca->e - ca->s) : deep_;
  //   } else {
  //     ca->e = 0;
  //     ca->siz = 0;
  //   }
  // };

  // read_data(&ca_near);
  // read_data(&ca_far);
  // DO NOT REMOVE FOR SAVE SAPCE
}

int
Force::new_force_data(const ForceLevel& flv_79,
                      const time_t tm,
                      const price_t hpx) noexcept
{
  if (!flv_79.d_far_10 || !flv_79.d_near_10)
    return (0);

  const dir_t d_near = flv_79.get_near_dir();
  const int8_t l_near = abs(flv_79.l_near_10);
  const int8_t l_near_rt = abs(flv_79.l_near_rt);
  const dir_t d_far = flv_79.get_far_dir();
  const int8_t l_far = abs(flv_79.l_far_10);
  const int8_t l_far_rt = abs(flv_79.l_far_rt);
  ForceSpot fs;

  auto new_near_spot = [&]() {
    fs.d = d_near;
    fs.l = l_near;
    fs.l_max = l_near_rt;
    fs.t_max = fs.t = tm;
    fs.p_max = fs.p = hpx;

    return ca_new_data(&ca_near, &fs);
  };
  auto new_far_spot = [&]() {
    fs.d = d_far;
    fs.l = l_far;
    fs.l_max = l_far_rt;
    fs.t_max = fs.t = tm;
    fs.p_max = fs.p = hpx;

    return ca_new_data(&ca_far, &fs);
  };
  auto update_near_spot = [&](auto spot) {
    if (spot->l < l_near || spot->l_max < l_near_rt) {
      spot->l_max = l_near_rt;
      spot->p_max = hpx;
      spot->t_max = tm;
    }
  };
  auto update_far_spot = [&](auto spot) {
    if (spot->l < l_far || spot->l_max < l_far_rt) {
      spot->l_max = l_far_rt;
      spot->p_max = hpx;
      spot->t_max = tm;
    }
  };

  if (d_near) {
    ca_iter ca_it = near_current();
    if (ca_it == ca_iter_end) {
      ca_it = new_near_spot();
    } else {
      ForceSpot* spot = get_near_force_spot(ca_it);
      if (spot->d != d_near) {
        ca_it = new_near_spot();
      } else {
        update_near_spot(spot);
      }
    }
  }

  if (d_far) {
    auto ca_it = far_current();
    if (ca_it == ca_iter_end) {
      new_far_spot();
    } else {
      ForceSpot* spot = get_far_force_spot(ca_it);
      if (spot->d != d_far) {
        new_far_spot();
      } else {
        update_far_spot(spot);
      }
    }
  }

  return (0);
}

void
Force::clone_data(const Force& force) noexcept
{
  memcpy((void*)(this), &force, sizeof(Force));
  read_data();
}

void
Force::print(const price_t hpx, const time_t t_now) const noexcept
{
  auto p = [&](const CircleArray* ca) {
    auto it = ca_back(ca);
    auto it_next = ca_iter_end;
    for (; it != ca_iter_end; it = ca_prev_(ca, it)) {
      ForceSpot* fs = CA_GET_FORCE(ca, it);
      if (it_next != ca_iter_end) {
        CA_GET_FORCE(ca, it_next)->print_diff(fs);
      } else {
        fs->print(hpx, t_now);
      }
      it_next = it;
    }
    printf("\n");
  };

  printf("Force Near: ");
  p(&ca_near);
  printf("Force Far: ");
  p(&ca_far);
}

} // namespace Trade
